#!/bin/python3
# _*_ coding: utf-8 _*_

import argparse
import socket
import socketserver
import http.server
import os
import re



import struct
import fcntl
def get_interface_ip(ifname, family):
    """Retrieve the IP address associated with a network interface."""
    s = socket.socket(family, socket.SOCK_DGRAM)
    try:
        if family == socket.AF_INET:  # IPv4
            return socket.inet_ntoa(fcntl.ioctl(
                s.fileno(),
                0x8915,  # SIOCGIFADDR for IPv4
                struct.pack('256s', bytes(ifname[:15], 'utf-8'))
            )[20:24])
        elif family == socket.AF_INET6:  # IPv6
            return socket.inet_ntop(
                socket.AF_INET6,
                fcntl.ioctl(
                    s.fileno(),
                    0x8915,  # SIOCGIFADDR for IPv6 might need to be different
                    struct.pack('256s', bytes(ifname[:15], 'utf-8'))
                )[20:36]
            )
    except IOError:
        return None

def list_all_ips():
    """List all IP addresses for all network interfaces (IPv4 and IPv6)."""
    addresses = []
    interfaces = socket.if_nameindex()

    for interface in interfaces:
        # Get IPv4 address for the interface
        ipv4 = get_interface_ip(interface[1], socket.AF_INET)
        if ipv4 and ipv4 != '127.0.0.1':
            addresses.append((interface[1], ipv4, 'IPv4'))

        # Get IPv6 address for the interface
        ipv6 = get_interface_ip(interface[1], socket.AF_INET6)
        if ipv6 and not ipv6.startswith('fe80'):  # Filter out link-local addresses
            addresses.append((interface[1], ipv6, 'IPv6'))

    return addresses

class ForkingHTTPServer(socketserver.ForkingTCPServer):
    allow_reuse_address = 1
    address_family = socket.AF_INET6
    
    def server_bind(self):
        socketserver.TCPServer.server_bind(self)
        host, port = self.socket.getsockname()[:2]
        self.server_name = socket.getfqdn(host)
        self.server_port = port


class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    
    def send_head(self):
        """Override the default method to handle Range requests."""
        path = self.translate_path(self.path)
        
        if os.path.isdir(path):
            index = os.path.join(path, 'index.html')
            if os.path.exists(index):
                path = index  # Serve index.html if it exists
            else:
                return self.list_directory(path)  # List directory if no index.html
    
        try:
            file = open(path, 'rb')
        except OSError:
            self.send_error(404, "File not found")
            return None

        file_size = os.path.getsize(path)
        range_header = self.headers.get('Range')
    
        if range_header:
            # Parsing the Range header, e.g., bytes=500-999
            range_match = re.match(r"bytes=(\d+)-(\d*)", range_header)
            if range_match:
                start = int(range_match.group(1))
                end = range_match.group(2)
                end = int(end) if end else file_size - 1
    
                if start >= file_size:
                    self.send_error(416, "Requested Range Not Satisfiable")
                    self.send_header("Content-Range", f"bytes */{file_size}")
                    self.end_headers()
                    return None
    
                self.send_response(206)
                self.send_header("Content-Type", self.guess_type(path))
                self.send_header("Content-Range", f"bytes {start}-{end}/{file_size}")
                self.send_header("Content-Length", str(end - start + 1))
                self.send_header("Accept-Ranges", "bytes")
                self.end_headers()
    
                # Serve the requested file range
                file.seek(start)
                self.wfile.write(file.read(end - start + 1))
                file.close()
                return None
    
        # Default response when no Range request is made (serve the full file)
        self.send_response(200)
        self.send_header("Content-Type", self.guess_type(path))
        self.send_header("Content-Length", str(file_size))
        self.send_header("Accept-Ranges", "bytes")
        self.end_headers()
    
        return file
    


def server(port, address, handler_class, server_class, directory):
    if address == '':
        address = '::'
    server_address = (address, port)
    httpd = server_class(server_address, handler_class)

    # Print startup information
    print("="*50)
    print(f"Starting server on:")
    print(f"  Address : {address}")
    print(f"  Port    : {port}")
    print(f"  Root    : {directory}")
    print(f"  Handler : {handler_class.__name__}")
    print(f'-------->> Now, listening at port {port}...')

    # List all IP addresses
    print('-------->> Available IP addresses:')
    for interface, ip, version in list_all_ips():
        print(f"-------->> {interface} ({version}): http://{ip}:{port}")
    print("="*50)

    httpd.serve_forever()


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Run a simple HTTP server.')
    parser.add_argument('port', type=int, nargs='?', default=8000, help='Port to listen on (default: 8000)')
    parser.add_argument('-b', '--bind', default='', help='Address to bind to (default: ::)')
    parser.add_argument('-d', '--directory', default='.', help='Directory to serve (default: current directory)')
    parser.add_argument('-p', '--protocol', choices=['http'], default='http', help='Protocol version (default: http)')
    parser.add_argument('--cgi', action='store_true', help='Enable CGI')

    args = parser.parse_args()

    if args.protocol != 'http':
        raise ValueError("Only 'http' protocol is supported")

    # Change working directory to the one specified by the -d option
    os.chdir(args.directory)

    if args.cgi:
        handler_class = http.server.CGIHTTPRequestHandler
        handler_class.cgi_directories = {"/cgi-bin": "/cgi-bin"}  # Adjust as needed
    else:
        handler_class = CustomHTTPRequestHandler

    server(args.port, address=args.bind, handler_class=handler_class, server_class=ForkingHTTPServer, directory=args.directory)
